WhereSQLBuilderFactory.java
package org.codefilarete.stalactite.query.builder;
import org.codefilarete.stalactite.query.builder.FunctionSQLBuilderFactory.FunctionSQLBuilder;
import org.codefilarete.stalactite.query.builder.OperatorSQLBuilderFactory.OperatorSQLBuilder;
import org.codefilarete.stalactite.query.model.AbstractCriterion;
import org.codefilarete.stalactite.query.model.ColumnCriterion;
import org.codefilarete.stalactite.query.model.ConditionalOperator;
import org.codefilarete.stalactite.query.model.CriteriaChain;
import org.codefilarete.stalactite.query.model.LogicalOperator;
import org.codefilarete.stalactite.query.model.RawCriterion;
import org.codefilarete.stalactite.query.model.Selectable;
import org.codefilarete.stalactite.query.model.operator.BiOperandOperator;
import org.codefilarete.stalactite.query.model.operator.SQLFunction;
import org.codefilarete.stalactite.query.model.operator.TupleIn;
import org.codefilarete.stalactite.sql.ddl.JavaTypeToSqlTypeMapping;
import org.codefilarete.stalactite.sql.statement.SQLStatement.BindingException;
import org.codefilarete.stalactite.sql.statement.binder.ColumnBinderRegistry;
import org.codefilarete.tool.Reflections;
/**
* Factory for {@link WhereSQLBuilder}. It's overridable through {@link org.codefilarete.stalactite.sql.Dialect}
* which then let one gives its own implementation of {@link WhereSQLBuilderFactory}.
*
* @author Guillaume Mary
* @see #whereBuilder(CriteriaChain, DMLNameProvider, QuerySQLBuilderFactory)
*/
public class WhereSQLBuilderFactory {
public static final String AND = "and";
public static final String OR = "or";
private final ColumnBinderRegistry parameterBinderRegistry;
private final OperatorSQLBuilderFactory operatorSqlBuilderFactory;
private final FunctionSQLBuilderFactory functionSQLBuilderFactory;
public WhereSQLBuilderFactory(JavaTypeToSqlTypeMapping javaTypeToSqlTypeMapping, ColumnBinderRegistry parameterBinderRegistry) {
this(parameterBinderRegistry,
new OperatorSQLBuilderFactory(),
new FunctionSQLBuilderFactory(javaTypeToSqlTypeMapping));
}
public WhereSQLBuilderFactory(ColumnBinderRegistry parameterBinderRegistry,
OperatorSQLBuilderFactory operatorSqlBuilderFactory,
FunctionSQLBuilderFactory functionSQLBuilderFactory) {
this.parameterBinderRegistry = parameterBinderRegistry;
this.operatorSqlBuilderFactory = operatorSqlBuilderFactory;
this.functionSQLBuilderFactory = functionSQLBuilderFactory;
}
/**
* Constructs a {@link WhereSQLBuilder} for building SQL WHERE clauses based on the provided criteria.
*
* @param where the criteria chain representing the conditions to be included in the WHERE clause
* @param dmlNameProvider the instance that supplies names for database objects (e.g., tables, columns) in SQL statements
* @param querySQLBuilderFactory a factory for printing instances of {@link org.codefilarete.stalactite.query.model.QueryStatement}s used in subqueries for "in" operator
* @return an instance of {@link WhereSQLBuilder} configured with the provided criteria and supporting components
*/
public WhereSQLBuilder whereBuilder(CriteriaChain where, DMLNameProvider dmlNameProvider, QuerySQLBuilderFactory querySQLBuilderFactory) {
FunctionSQLBuilder functionSQLBuilder = functionSQLBuilderFactory.functionSQLBuilder(dmlNameProvider);
return new WhereSQLBuilder(
where,
dmlNameProvider,
parameterBinderRegistry,
operatorSqlBuilderFactory.operatorSQLBuilder(functionSQLBuilder, querySQLBuilderFactory),
functionSQLBuilder);
}
/**
* Formats {@link CriteriaChain} object to an SQL {@link String} through {@link #toSQL()}.
*
* @author Guillaume Mary
* @see #toSQL()
*/
public static class WhereSQLBuilder implements SQLBuilder, PreparableSQLBuilder {
private final CriteriaChain where;
private final DMLNameProvider dmlNameProvider;
private final ColumnBinderRegistry parameterBinderRegistry;
private final OperatorSQLBuilder operatorSqlBuilder;
private final FunctionSQLBuilder functionSQLBuilder;
public WhereSQLBuilder(CriteriaChain where,
DMLNameProvider dmlNameProvider,
ColumnBinderRegistry parameterBinderRegistry,
OperatorSQLBuilder operatorSqlBuilder,
FunctionSQLBuilder functionSQLBuilder) {
this.where = where;
this.dmlNameProvider = dmlNameProvider;
this.parameterBinderRegistry = parameterBinderRegistry;
this.operatorSqlBuilder = operatorSqlBuilder;
this.functionSQLBuilder = functionSQLBuilder;
}
@Override
public String toSQL() {
StringSQLAppender result = new StringSQLAppender(dmlNameProvider);
appendTo(result);
return result.getSQL();
}
@Override
public ExpandableSQLAppender toPreparableSQL() {
ExpandableSQLAppender preparedSQLAppender = new ExpandableSQLAppender(parameterBinderRegistry, dmlNameProvider);
appendTo(preparedSQLAppender);
return preparedSQLAppender;
}
public void appendTo(SQLAppender sql) {
WhereAppender whereAppender = new WhereAppender(sql, operatorSqlBuilder, functionSQLBuilder);
whereAppender.cat(where);
}
public class WhereAppender {
private final SQLAppender sql;
private final OperatorSQLBuilder operatorSqlBuilder;
private final FunctionSQLBuilder functionSQLBuilder;
public WhereAppender(SQLAppender sql,
OperatorSQLBuilder operatorSqlBuilder,
FunctionSQLBuilder functionSQLBuilder) {
this.sql = sql;
this.operatorSqlBuilder = operatorSqlBuilder;
this.functionSQLBuilder = functionSQLBuilder;
}
/**
* Main entry point
* @param criteria the conditions that must be appended
*/
public void cat(CriteriaChain criteria) {
boolean isNotFirst = false;
for (Object criterion : criteria) {
if (isNotFirst) {
cat(((AbstractCriterion) criterion).getOperator());
} else {
isNotFirst = true;
}
cat(criterion);
}
}
private void cat(Object o) {
if (o instanceof CharSequence) {
sql.cat(o.toString());
} else if (o instanceof CriteriaChain) {
sql.cat("(");
cat((CriteriaChain) o);
sql.cat(")");
} else if (o instanceof RawCriterion) {
for (Object conditionItem : ((RawCriterion) o).getCondition()) {
catConditionItem(conditionItem);
}
} else if (o instanceof ColumnCriterion) {
cat((ColumnCriterion) o);
}
}
private void catConditionItem(Object conditionItem) {
if (conditionItem instanceof ColumnCriterion) {
cat((ColumnCriterion) conditionItem);
} else if (conditionItem instanceof CharSequence) {
sql.cat(conditionItem.toString());
} else if (conditionItem instanceof CriteriaChain) {
sql.cat("(");
cat((CriteriaChain) conditionItem);
sql.cat(")");
} else if (conditionItem instanceof SQLFunction) { // made for having(sum(col), eq(..))
cat((SQLFunction) conditionItem);
} else if (conditionItem instanceof Selectable) {
cat((Selectable) conditionItem);
} else if (conditionItem instanceof TupleIn) { // "if" to be done before "if" about ConditionalOperator to take inheritance into account
catTupledIn((TupleIn) conditionItem);
} else if (conditionItem instanceof ConditionalOperator) {
cat((ConditionalOperator) conditionItem);
} else {
throw new UnsupportedOperationException("Unknown criterion type " + Reflections.toString(conditionItem.getClass()));
}
}
public void cat(ColumnCriterion criterion) {
Object condition = criterion.getCondition();
if (condition instanceof BiOperandOperator) { // "if" to be done before "if" about ConditionalOperator to take inheritance into account
cat(criterion.getColumn(), (BiOperandOperator) condition);
} else {
cat(criterion.getColumn());
sql.cat(" ");
if (condition instanceof CharSequence) {
sql.cat(condition.toString());
} else if (condition instanceof ConditionalOperator) {
cat(criterion.getColumn(), (ConditionalOperator) condition);
} else {
try {
parameterBinderRegistry.getBinder(condition.getClass());
} catch (BindingException e) {
throw new IllegalArgumentException("Unknown criterion type " + Reflections.toString(condition.getClass()));
}
sql.catValue(condition);
}
}
}
public void cat(Selectable column) {
// delegated to SQLAppender
sql.catColumn(column);
}
public void cat(LogicalOperator operator) {
if (operator != null) {
sql.cat(" ", getName(operator), " ");
}
}
public void cat(Selectable c, BiOperandOperator operator) {
for (Object o : operator.asRawCriterion(c)) {
catConditionItem(o);
sql.cat(" ");
}
sql.removeLastChars(1);
}
public void cat(ConditionalOperator operator) {
operatorSqlBuilder.cat(operator, sql);
}
public void catTupledIn(TupleIn operator) {
operatorSqlBuilder.catTupledIn(operator, sql);
}
public void cat(SQLFunction sqlFunction) {
functionSQLBuilder.cat(sqlFunction, sql);
}
public void cat(Selectable column, ConditionalOperator operator) {
operatorSqlBuilder.cat(column, operator, sql);
}
public String getName(LogicalOperator operator) {
switch (operator) {
case AND:
return AND;
case OR:
return OR;
default:
throw new IllegalArgumentException("Operator " + operator + " is unknown");
}
}
}
}
}